﻿
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

namespace Conmmon
{
    public static class RabbitMQHelper
    {
        //rabbitmq 配置信息
        private static RabbitMQEntity _rabbitMQ = new RabbitMQEntity();

        private static ConnectionFactory _connectionFactory = new ConnectionFactory();

        private static IConnection connection;

        private static IModel channel;

        /// <summary>
        /// 队列缓存
        /// </summary>
        private static Hashtable MQCache = Hashtable.Synchronized(new Hashtable());


        static RabbitMQHelper()
        {
            _rabbitMQ = ConfigHelper.GetSectionValue<RabbitMQEntity>("RabbitMQ");

            _connectionFactory = new ConnectionFactory()
            {
                HostName = _rabbitMQ.HostName,
                Port = Convert.ToInt32(_rabbitMQ.Port),
                UserName = _rabbitMQ.UserName,
                Password = _rabbitMQ.Password,
                VirtualHost = _rabbitMQ.VirtualHost,
            };
        }

        /// <summary>
        /// 获取ConnectionFactory对象
        /// </summary>
        /// <returns></returns>
        public static ConnectionFactory GetConnectionFactory()
        {
            // return _connectionFactory;

            return new ConnectionFactory()
            {
                HostName = _rabbitMQ.HostName,
                Port = Convert.ToInt32(_rabbitMQ.Port),
                UserName = _rabbitMQ.UserName,
                Password = _rabbitMQ.Password,
                VirtualHost = _rabbitMQ.VirtualHost,
            };
        }

        #region TCP长连接

        /// <summary>
        /// 获取TCP长连接 并且创建IModel
        /// </summary>
        /// <returns></returns>
        public static IConnection GetConnection()
        {
            if (connection == null)
            {
                //创建TCP长链接
                connection = GetConnectionFactory().CreateConnection();
            }

            if (channel == null)
            {
                //创建通道，相当于TCP中虚拟链接
                channel = connection.CreateModel();
            }

            return connection;

        }

        /// <summary>
        /// 关闭TCP长连接
        /// </summary>
        /// <returns></returns>
        public static void CloseConnection(IConnection connection)
        {
            connection.Close();
        }

        #endregion




        /// <summary>
        /// 生产者创建队列 
        /// </summary>
        /// <param name="message">队列信息</param>
        /// <param name="queue">队列名称</param>
        /// <param name="exchange">交换机名称</param>
        /// <param name="routingKey">路由KEY</param>
        public static void Producer(string message, string queue, string exchange, string routingKey, IDictionary<string, object> props, int Expiration)
        {

            //创建TCP长链接
            GetConnection();

            //生成队列缓存Key
            string MQCacheKey = $"{queue}{exchange}{routingKey}{JsonConvert.SerializeObject(props)}{Expiration}";

            var properties = channel.CreateBasicProperties();

            //如果已经创建过队列则无需生成
            if (MQCache.ContainsKey(MQCacheKey))
            {
                properties.Expiration = Expiration.ToString();
                channel.BasicPublish(exchange, routingKey == "" ? queue : routingKey, properties, Encoding.UTF8.GetBytes(message));
                return;
            }

            MQCache[MQCacheKey] = "";
            /**
            * 
            * 创建队列，声明并创建一个队列，如果队列已存在，这使用这个队列
            * 第一个参数：队列ID
            * 第二个参数：是否持久化，false对应不持久化数据,MQ停止之后数据就会丢失
            * 第三个参数：是否队列私有化，false则代表所有的消费者都可以访问，true代表只有第一次拥有它的消费者才能一直使用
            * 第四个参数: 是否自动删除，false代表链接停掉之后不自动删除这个队列
            * 其他额外参数为null
            * 
            * */
            var delayChannel = connection.CreateModel();//延时队列连接通道
            if (props.Count > 0)
            {
                string deadExchange = props["x-dead-letter-exchange"]?.ToString();
                string deadRoutingKey = props["x-dead-letter-routing-key"]?.ToString();
                if (!string.IsNullOrEmpty(deadExchange) && !string.IsNullOrEmpty(deadRoutingKey))
                {
                    string delayQueue = queue + "-delay";
                    delayChannel.ExchangeDeclare(deadExchange, "direct");//创建交换器
                    delayChannel.QueueDeclare(delayQueue, true, false, false, null);
                    delayChannel.QueueBind(delayQueue, deadExchange, deadRoutingKey);
                    //最多接受条数 0为无限制，每次消费消息数(根据实际场景设置)，true=作用于整channel,false=作用于具体的消费者
                    delayChannel.BasicQos(0, 10, false);
                }
            }

            channel.QueueDeclare(queue, true, false, false, props);

            // var properties = channel.CreateBasicProperties();
            properties.Expiration = Expiration.ToString();
            channel.BasicPublish(exchange, routingKey == "" ? queue : routingKey, properties, Encoding.UTF8.GetBytes(message));
        }

        /// <summary>
        /// 消费者
        /// </summary>
        /// <param name="queue">队列名称</param>
        /// <param name="exchange">交换机名称</param>
        /// <param name="routingKey">路由KEY</param>
        /// <returns>返回队列消息内容</returns>
        public static RabbitChannelConfig Consunmer(string queue, string exchange, string routingKey, IDictionary<string, object> props)
        {
            //获取TCP长连接以及创建IModel
            GetConnection();


            /**
             * 创建队列，声明并创建一个队列，如果队列已存在，这使用这个队列
             * 第一个参数：队列ID
             * 第二个参数：是否持久化，false对应不持久化数据,MQ停止之后数据就会丢失
             * 第三个参数：是否队列私有化，false则代表所有的消费者都可以访问，true代表只有第一次拥有它的消费者才能一直使用
             * 第四个参数: 是否自动删除，false代表链接停掉之后不自动删除这个队列
             * 其他额外参数为null
             * **/
            channel.BasicQos(0, 1, false);
            channel.QueueDeclare(queue, true, false, false, props);

            /**
             * 从MQ服务器中获取数据
             * 创建一个消息消费者
             * 第一个参数：队列名
             * 第二个参数：是否自动确认收到消息，false代表手动确认消息，这个是MQ推荐的做法
             * 第三个参数：要传入IBasicConsumer接口
             *
             **/
            //创建事件
            EventingBasicConsumer comsumers = new EventingBasicConsumer(channel);
            channel.BasicConsume(queue, false, comsumers);

            //if (!string.IsNullOrEmpty(exchange))
            //{
            //    channel.QueueBind(queue, exchange, routingKey);
            //}

            RabbitChannelConfig rabbitChannelConfig = new RabbitChannelConfig();
            comsumers.Received += rabbitChannelConfig.Received;


            return rabbitChannelConfig;
        }

    }

    public class RabbitChannelConfig
    {

        public Func<RabbitMessageEntity, bool> OnCallback { get; set; }

        public void Received(object sender, BasicDeliverEventArgs args)
        {
            RabbitMessageEntity rabbitMessage = new RabbitMessageEntity();
            try
            {
                rabbitMessage.Content = Encoding.UTF8.GetString(args.Body.ToArray());
                rabbitMessage.Consumer = (EventingBasicConsumer)sender;
                rabbitMessage.BasicDeliver = args;

                //调用委托 返回结果为是否签收消息
                var res = OnCallback.Invoke(rabbitMessage);

                if (res)
                {
                    /**
                     * 确认签收当前消息
                     * 第一个参数 事件标签
                     * 第二个参数 是否确认多个以传递的消息
                     **/
                    rabbitMessage.Consumer.Model.BasicAck(args.DeliveryTag, false);
                }
                else
                {
                    //拒绝签收消息
                    rabbitMessage.Consumer.Model.BasicNack(args.DeliveryTag, false, false);
                }
            }
            catch (Exception e)
            {
                rabbitMessage.Exception = e;
                rabbitMessage.Code = 500;
                /**
                 * 第一个参数 事件标签
                 * 第二个参数 是否拒绝多个以传递的消息
                 * 第三个参数 是否重新排队
                 * **/
                rabbitMessage.Consumer.Model.BasicNack(args.DeliveryTag, false, false);
            }
        }
    }

    public class RabbitMessageEntity
    {
        public string Content { get; set; }
        public EventingBasicConsumer Consumer { get; set; }
        public BasicDeliverEventArgs BasicDeliver { get; set; }
        public string ErrorMessage { get; set; }
        public Exception Exception { get; set; }
        public int Code { get; set; }
    }
}
